package com.kevin.v2.spring.formework.aop.support;

import com.kevin.v2.spring.formework.aop.aspect.KvMethodAfterAdvice;
import com.kevin.v2.spring.formework.aop.aspect.KvMethodBeforeAdvice;
import com.kevin.v2.spring.formework.aop.aspect.KvMethodThrowingAdvice;
import com.kevin.v2.spring.formework.aop.config.KvConfig;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 用于封装要 被织入 的所有
 */
public class KvAdviseSupport {
   //被代理对象class
   private Class targetClass;
   //被代理对象
   private Object target;

   //要切入的 正则 execution pintCut
   private Pattern pointCutClasspattern;

   //保存要被切入的方法和 需要调用通知方法的集合链
   private transient Map<Method, List<Object>> methodCache;

   //读取到的aop配置文件
   private KvConfig config;


   public void parse () {
      try {

         //pointCut 表达式 转换成java 的正则
         String pointCut = config.getPointCut()
                 .replaceAll("\\.", "\\\\.")
                 .replaceAll("\\\\.\\*", ".*")
                 .replaceAll("\\(", "\\\\(")
                 .replaceAll("\\)", "\\\\)");
         //拿到切面正则表达式 转换后的正则
         Pattern pattern = Pattern.compile(pointCut);

         String pointCutForClass = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4);
         //转换成 匹配每个类的正则
         pointCutClasspattern = Pattern.compile("class " + pointCutForClass.substring(pointCutForClass.lastIndexOf(" ") + 1));
         methodCache = new HashMap<>();

         //拿到通知类的class
         Class aspectClass = Class.forName(config.getAspectClass());
         //保存所有的通知方法
         Map<String, Method> aspectMethods = new HashMap<>();
         for (Method method : aspectClass.getMethods()) {
            aspectMethods.put(method.getName(), method);
         }

         //循环代理对象的所有方法,和通知(织入)方法做关联
         for (Method method : targetClass.getMethods()) {
            String methodToString = method.toString();
            if (methodToString.contains("throws")) {

               methodToString = methodToString.substring(0, methodToString.lastIndexOf("throws")).trim();
            }
            //匹配要被通知的方法
            Matcher matcher = pattern.matcher(methodToString);
            if (matcher.matches()) {
               List<Object> adivcesList = new ArrayList<>();
               if (!(null == config.getAspectBefore() || "".equals(config.getAspectBefore().trim()))) {
                  adivcesList.add(new KvMethodBeforeAdvice(aspectMethods.get(config.getAspectBefore()), aspectClass.newInstance()));
               }
               if (!(null == config.getAspectAfter() || "".equals(config.getAspectAfter().trim()))) {
                  adivcesList.add(new KvMethodAfterAdvice(aspectMethods.get(config.getAspectBefore()), aspectClass.newInstance()));
               }
               if (!(null == config.getAspectAfterThrow() || "".equals(config.getAspectAfterThrow().trim()))) {
                  KvMethodThrowingAdvice kvMethodThrowingAdvice = new KvMethodThrowingAdvice(aspectMethods.get(config.getAspectAfterThrow()), aspectClass.newInstance());
                  kvMethodThrowingAdvice.setThrowingName(config.getAspectAfterThrowingName());
                  adivcesList.add(kvMethodThrowingAdvice);

               }
               //将被通知的方法 和通知方法集合做管理缓存起来
               methodCache.put(method, adivcesList);
            }


         }
      } catch (Exception e) {
         e.printStackTrace();
      }

   }

   /**
    * 匹配某个类是否被切面
    *
    * @return
    */
   public boolean pointCutMatch () {
      return pointCutClasspattern.matcher(this.targetClass.toString()).matches();
   }

   /**
    * 获取一个被代理的方法所有要通知的方法
    *
    * @param method      被代理的方法
    * @param targetClass 被代理的类
    * @return
    */
   public List<Object> getInterceptorAndJdkInterceptionAdvice (Method method, Class<?> targetClass) throws NoSuchMethodException {

      List<Object> cached = methodCache.get(method);
      if (cached == null) {

         Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());

         cached = methodCache.get(m);

         methodCache.put(m, cached);
      }
      return cached;

   }


   public KvAdviseSupport (KvConfig config) {
      this.config = config;
   }

   public Class getTargetClass () {
      return targetClass;
   }

   public void setTargetClass (Class targetClass) {
      this.targetClass = targetClass;
      parse();
   }

   public KvConfig getConfig () {
      return config;
   }

   public Object getTarget () {
      return target;
   }

   public void setTarget (Object target) {
      this.target = target;
   }
}
